package com.xpj.template;

import com.github.benmanes.caffeine.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Slf4j
@Component
public class CacheTemplate {


    @Autowired
    private LoadingCache<String, Object> localCache;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ScheduledExecutorService scheduledExecutorService;

    // 空值标记（用于区分真正的null和未缓存）
    private static final Object NULL_VALUE = new Object();
    @Autowired
    private LoadingCache loadingCache;

    public <T> T query(String key, long ttl, Supplier<T> loader, RBloomFilter<String> bloomFilter) {

        // 1. 先查本地缓存
        Object localCacheValue = localCache.getIfPresent(key);
        if (localCacheValue != null) {
            return parseValue(localCacheValue);
        }

        // 2. 布隆过滤器拦截（如果启用） 待完善
        if (bloomFilter != null && !bloomFilter.contains(key)) {
            log.warn("布隆过滤器拦截无效数据: {}", key);
            return null;
        }

        //3. 查Redis缓存
        Object redisValue = redisTemplate.opsForValue().get(key);
        if (redisValue != null){
            // 重新续期，这样热点数据始终在redis中
            redisTemplate.expire(key, ttl, TimeUnit.SECONDS);

            //放入本地缓存
            loadingCache.put(key, redisValue);
            return parseValue(redisValue);
        }

        //4. 缓存未命中，查询数据库（带互斥锁防击穿）
        RLock lock = redissonClient.getLock("mutex:lock:" + key);
        try {
            // 尝试获取锁，最多等待3秒
//            boolean acquired = lock.tryLock(3, TimeUnit.SECONDS);
//            if (!acquired) {
//                // 获取锁失败，说明系统压力大或出现故障，返回服务器繁忙
//                log.warn("获取分布式锁超时，key: {}，系统压力过大，返回服务器繁忙", key);
//                throw new RuntimeException("服务器繁忙，请稍后再试");
//            }
            //直接加锁 与上面根据实际情况选择一种
            lock.lock();
            redisValue = redisTemplate.opsForValue().get(key);
            if (redisValue != null){
                return parseValue(redisValue);
            }

            T dbValue = loader.get();

            Object valueToCache = dbValue == null ? NULL_VALUE : dbValue;

            redisTemplate.opsForValue().set(key, valueToCache, ttl, TimeUnit.SECONDS);
            localCache.put(key, valueToCache);

            return dbValue;

        }/*catch (InterruptedException e){
            log.error("获取分布式锁被中断，key: {}", key, e);
            Thread.currentThread().interrupt();
            // 被中断时也直接返回服务器繁忙
            throw new RuntimeException("服务器繁忙，请稍后再试");
        }*/finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

    }


    private <T> T parseValue(Object value){
        if (value == NULL_VALUE) {
            return null;
        }
        return (T) value;
    }

    /**
     * 删除缓存
     * @param key 缓存键
     * @param delay 延迟删除等待时间 单位：毫秒
     */
    public void delete(String key, long delay){
        delete(key);

        if (delay > 0) {
            scheduledExecutorService.schedule(() -> {
                try {
                    //延迟删除缓存
                    delete(key);
                    log.debug("延迟删除缓存成功，key：{}，延迟：{}ms", key, delay);
                } catch (Exception e) {
                    log.error("延迟删除缓存失败，key：{}", key, e);
                }
            }, delay, TimeUnit.MILLISECONDS);
        }
        log.debug("删除缓存，key: {}", key);
    }

    private void delete(String key) {
        //删除redis缓存
        redisTemplate.delete(key);
        //删除本地缓存
        localCache.invalidate(key);
    }

    /**
     * 强制写入缓存
     * @param cacheKey
     * @param value
     * @param ttl
     */
    public void forcePut(String cacheKey, Object value, int ttl) {

        Object valueToCache = value == null ? NULL_VALUE : value;

        // 写入Redis
        redisTemplate.opsForValue().set(cacheKey, valueToCache, ttl, TimeUnit.SECONDS);

        // 写入本地缓存
        localCache.put(cacheKey, valueToCache);

        log.debug("强制写入缓存，key: {}, ttl: {}秒", cacheKey, ttl);
    }
}
